Generalize xmlgeneric callbacks (#532)
authortsteven4 <13596209+tsteven4@users.noreply.github.com>
Mon, 13 Apr 2020 14:27:57 +0000 (08:27 -0600)
committerGitHub <noreply@github.com>
Mon, 13 Apr 2020 14:27:57 +0000 (08:27 -0600)
generalize callbacks for xmlgeneric.

use newly available pointer to data member callbacks to restore
yahoo functionality.

add a yahoo test.
.
add xml_init method to automatically generate Functors.

reference/yahoo.csv [new file with mode: 0644]
reference/yahoo.xml [new file with mode: 0644]
testo.d/yahoo.test [new file with mode: 0644]
xmlgeneric.cc
xmlgeneric.h
yahoo.cc
yahoo.h

diff --git a/reference/yahoo.csv b/reference/yahoo.csv
new file mode 100644 (file)
index 0000000..a8f590e
--- /dev/null
@@ -0,0 +1,2 @@
+No,Latitude,Longitude,Name\r
+1,37.416384,-122.024853,"701 FIRST AVE, SUNNYVALE, CA, 94089-1019, US"\r
diff --git a/reference/yahoo.xml b/reference/yahoo.xml
new file mode 100644 (file)
index 0000000..7425755
--- /dev/null
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<ResultSet xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+xmlns="urn:yahoo:maps"
+xsi:schemaLocation="urn:yahoo:maps http://local.yahooapis.com/MapsService/V1/GeocodeResponse.xsd">
+  <Result precision="address">
+<Latitude>37.416384</Latitude>
+
+<Longitude>-122.024853</Longitude>
+<Address>701 FIRST AVE</Address>
+<City>SUNNYVALE</City>
+<State>CA</State>
+
+<Zip>94089-1019</Zip>
+<Country>US</Country>
+  </Result>
+</ResultSet>
diff --git a/testo.d/yahoo.test b/testo.d/yahoo.test
new file mode 100644 (file)
index 0000000..a5e8bdc
--- /dev/null
@@ -0,0 +1,4 @@
+
+gpsbabel -i yahoo -f ${REFERENCE}/yahoo.xml -o unicsv -F ${TMPDIR}/yahoo.csv
+compare ${REFERENCE}/yahoo.csv ${TMPDIR}/yahoo.csv
+
index 15c301959a57c71959d85e2a7c79be8bbefc287b..83bc27cdc83dacd7c17685daa79b0e60583dd15a 100644 (file)
@@ -23,6 +23,7 @@
 #include <QtCore/QHash>                 // for QHash
 #include <QtCore/QIODevice>             // for QIODevice, QIODevice::ReadOnly
 #include <QtCore/QLatin1Char>           // for QLatin1Char
+#include <QtCore/QList>
 #include <QtCore/QStringRef>            // for QStringRef
 #include <QtCore/QTextCodec>            // for QTextCodec
 #include <QtCore/QXmlStreamAttributes>  // for QXmlStreamAttributes
@@ -44,7 +45,8 @@ enum xg_shortcut {
   xg_shortcut_ignore
 };
 
-static xg_tag_mapping* xg_tag_tbl;
+static QList<xg_tag_map_entry>* xg_tag_tbl;
+static bool dynamic_tag_tbl;
 static QHash<QString, xg_shortcut>* xg_shortcut_taglist;
 
 static QString rd_fname;
@@ -65,25 +67,25 @@ static QTextCodec* codec = utf8_codec;  // Qt has no vanilla ASCII encoding =(
  * xml strains and insulates us from a lot of the grubbiness of expat.
  */
 
-xg_callback*
+XgCallbackBase*
 xml_tbl_lookup(const QString& tag, xg_cb_type cb_type)
 {
   const QByteArray key = tag.toUtf8();
   const char* keyptr = key.constData();
-  for (xg_tag_mapping* tm = xg_tag_tbl; tm->tag_cb != nullptr; ++tm) {
-    if ((cb_type == tm->cb_type) && str_match(keyptr, tm->tag_name)) {
-      return tm->tag_cb;
+  for (const auto& tm : qAsConst(*xg_tag_tbl)) {
+    if ((cb_type == tm.cb_type) && str_match(keyptr, tm.tag_name)) {
+      return tm.tag_cb;
     }
   }
   return nullptr;
 }
 
 void
-xml_init(const QString& fname, xg_tag_mapping* tbl, const char* encoding,
+xml_common_init(const QString& fname, const char* encoding,
          const char** ignorelist, const char** skiplist)
 {
   rd_fname = fname;
-  xg_tag_tbl = tbl;
+
   xg_encoding = encoding;
   if (encoding) {
     QTextCodec* tcodec = QTextCodec::codecForName(encoding);
@@ -91,6 +93,7 @@ xml_init(const QString& fname, xg_tag_mapping* tbl, const char* encoding,
       codec = tcodec;
     }
   }
+
   xg_shortcut_taglist = new QHash<QString, xg_shortcut>;
   if (ignorelist != nullptr) {
     for (; ignorelist && *ignorelist; ++ignorelist) {
@@ -104,14 +107,47 @@ xml_init(const QString& fname, xg_tag_mapping* tbl, const char* encoding,
   }
 }
 
+void
+xml_init(const QString& fname, QList<xg_tag_map_entry>* tbl, const char* encoding,
+         const char** ignorelist, const char** skiplist, bool dynamic_tbl)
+{
+  xg_tag_tbl = tbl;
+  dynamic_tag_tbl = dynamic_tbl;
+
+  xml_common_init(fname, encoding, ignorelist, skiplist);
+}
+
+void
+xml_init(const QString& fname, xg_tag_mapping* tbl, const char* encoding,
+         const char** ignorelist, const char** skiplist)
+{
+  xg_tag_tbl = new QList<xg_tag_map_entry>;
+  dynamic_tag_tbl = true;
+  for (xg_tag_mapping* tm = tbl; tm->tag_cb != nullptr; ++tm) {
+  auto* cb = new XgFunctionPtrCallback(tm->tag_cb);
+    xg_tag_tbl->append({cb, tm->cb_type, tm->tag_name});
+  }
+
+  xml_common_init(fname, encoding, ignorelist, skiplist);
+}
+
 void
 xml_deinit()
 {
+  if (dynamic_tag_tbl) {
+    for (const auto& tm : qAsConst(*xg_tag_tbl)) {
+      delete tm.tag_cb;
+    }
+    delete xg_tag_tbl;
+  }
+  xg_tag_tbl = nullptr;
+
   reader_data.clear();
   rd_fname.clear();
-  xg_tag_tbl = nullptr;
+
   xg_encoding = nullptr;
   codec = utf8_codec;
+
   delete xg_shortcut_taglist;
   xg_shortcut_taglist = nullptr;
 }
@@ -129,7 +165,7 @@ xml_shortcut(const QStringRef& name)
 static void
 xml_run_parser(QXmlStreamReader& reader)
 {
-  xg_callback* cb;
+  XgCallbackBase* cb;
   QString current_tag;
 
   while (!reader.atEnd()) {
@@ -163,7 +199,7 @@ xml_run_parser(QXmlStreamReader& reader)
       cb = xml_tbl_lookup(current_tag, cb_start);
       if (cb) {
         const QXmlStreamAttributes attrs = reader.attributes();
-        cb(nullptr, &attrs);
+        (*cb)(nullptr, &attrs);
       }
 
       cb = xml_tbl_lookup(current_tag, cb_cdata);
@@ -173,7 +209,7 @@ xml_run_parser(QXmlStreamReader& reader)
         // thus we will not process the EndElement case as we will issue a readNext first.
         // does a caller ever expect to be able to use both a cb_cdata and a
         // cb_end callback?
-        cb(c, nullptr);
+        (*cb)(c, nullptr);
         current_tag.chop(reader.qualifiedName().length() + 1);
       }
       break;
@@ -185,7 +221,7 @@ xml_run_parser(QXmlStreamReader& reader)
 
       cb = xml_tbl_lookup(current_tag, cb_end);
       if (cb) {
-        cb(reader.name().toString(), nullptr);
+        (*cb)(reader.name().toString(), nullptr);
       }
       current_tag.chop(reader.qualifiedName().length() + 1);
       break;
index c1e97fc824137819d834871c5929881bb6f6058c..c4e14dfe25e19d9b28a438d5f42254ff292ecc80 100644 (file)
 // be convenient to overload some day.
 using xg_string = const QString&;
 
-
 enum xg_cb_type {
   cb_start = 1,
   cb_cdata,
   cb_end,
 };
 
-using xg_callback = void (xg_string, const QXmlStreamAttributes*);
+class XgCallbackBase
+{
+public:
+  XgCallbackBase() = default;
+  virtual ~XgCallbackBase() = default;
+  XgCallbackBase(const XgCallbackBase&) = delete;
+  XgCallbackBase& operator=(const XgCallbackBase&) = delete;
+  XgCallbackBase(XgCallbackBase&&) = delete;
+  XgCallbackBase& operator=(XgCallbackBase&&) = delete;
+
+  virtual void operator()(xg_string string, const QXmlStreamAttributes* attrs) const = 0;
+};
+
+template<class XgFormat>
+class XgFunctor : public XgCallbackBase
+{
+public:
+  using XgCb = void (XgFormat::*)(xg_string, const QXmlStreamAttributes*);
+  XgFunctor(XgFormat* obj, XgCb cb) : that_(obj), cb_(cb) {}
+  void operator()(xg_string string, const QXmlStreamAttributes* attrs) const override
+  {
+    (that_->*cb_)(string, attrs);
+  }
 
+private:
+  XgFormat* that_;
+  XgCb cb_;
+};
+
+class XgFunctionPtrCallback : public XgCallbackBase
+{
+public:
+  using XgCb = void (xg_string, const QXmlStreamAttributes*);
+  explicit XgFunctionPtrCallback(XgCb cb) : cb_(cb) {}
+  void operator()(xg_string string, const QXmlStreamAttributes* attrs) const override
+  {
+    (*cb_)(string, attrs);
+  }
+
+private:
+  XgCb* cb_;
+};
+
+// xml processing uses a QList<xg_tag_map_entry>.
+// You may generated this yourself.  See method 1 below. 
+// Or it may be generated for you using one of the subsequent
+// methods.
+struct xg_tag_map_entry {
+  XgCallbackBase* tag_cb;
+  xg_cb_type cb_type;
+  const char* tag_name;
+};
+
+// Table generation from an array containing function pointers.
+// The above table can be generated by xml_init.  See method 2 below.
+// This is how things done historically before the Format class was
+// introduced.
+using xg_callback = void (xg_string, const QXmlStreamAttributes*);
 struct xg_tag_mapping {
   xg_callback* tag_cb;
   xg_cb_type cb_type;
   const char* tag_name;
 };
 
-extern const char* xhtml_entities;
+// Table generation from a list containing member function pointers.
+// The above table can be generated by xml_init.  See method 3 below.
+template<class MyFormat>
+struct xg_functor_map_entry {
+  using XgCb = void (MyFormat::*)(xg_string, const QXmlStreamAttributes*);
+  XgCb tag_cb;
+  xg_cb_type cb_type;
+  const char* tag_name;
+};
+
+template<class MyFormat, typename my_functor_map_entry>
+QList<xg_tag_map_entry>* build_xg_tag_map(MyFormat* instance, const QList<my_functor_map_entry>& map)
+{
+  auto* tag_tbl = new QList<xg_tag_map_entry>;
+  for (const auto& entry : qAsConst(map)) {
+    auto* tag_cb = new XgFunctor<MyFormat>(instance, entry.tag_cb);
+    tag_tbl->append({tag_cb, entry.cb_type, entry.tag_name});
+  }
+  return tag_tbl;
+}
 
+/*
+ * There are multiple ways to initialize with xml_init.
+ *
+ * 1. Build your own QList<xg_tag_map_entry>, and pass it.
+ *    You own the table, you must do any required clean up.
+ *    Your callbacks may be a mix of function pointers wrapped in XgFunctors
+ *    and non-static member functions wrapped in XgFunctionPtrCallbacks.
+ *    and XgFunctionPtrCallback(for static member functions or global functions) entries.
+ *    xml_init(fname, tbl, encoding, ignorelist, skiplist, false);
+ *    You must set the dynamic_tbl parameter to false so xml_deninit doesn't
+ *    attempt to free the table resources when xml_deinit is called.
+ *
+ * 2. Have xml_init build and own a table of XgFunctionPtrCallback entries
+ *    from an array of function pointers, i.e. a xg_tag_mapping array.
+ *    This only works when all callbacks are function pointers.
+ *    xml_init(fname, tbl, encoding, ignorelist, skiplist);
+ *    Generated table entries will automatically be freed.
+ *
+ * 3. Have xml_init build and own a table of XgFunctor entries from a list
+ *    of non-static member functions, i.e. a QList<my_functor_map_entry>.
+ *    This only works when all callbacks are non-static member functions.
+ *    xml_init(fname, build_xg_tag_map(instance, map), encoding, ignorelist, skiplist, true);
+ *    You must set the dynamic_tbl parameter to true to free the generated table
+ *    resources when xml_deinit is called.
+ *
+ */ 
+void xml_init(const QString& fname, QList<xg_tag_map_entry>* tbl, const char* encoding,
+              const char** ignorelist = nullptr,
+              const char** skiplist = nullptr, bool dynamic_tbl = false);
 void xml_init(const QString& fname, xg_tag_mapping* tbl,const char* encoding,
               const char** ignorelist = nullptr,
               const char** skiplist = nullptr);
index 72d312621fea4a6d65c5c9e9d5c4790bafaf5629..21c5200560bcb758b507a3f42c4b04bd1f75c600 100644 (file)
--- a/yahoo.cc
+++ b/yahoo.cc
  */
 
 
+#include <QtCore/QXmlStreamAttributes>  // for QXmlStreamAttributes
+
 #include "defs.h"
 #include "yahoo.h"
-#include "xmlgeneric.h"
-#include <QtCore/QXmlStreamAttributes>
+#include "xmlgeneric.h"                 // for xg_string, build_xg_tag_map, xml_deinit, xml_init, xml_read
 
 
 #define MYNAME "yahoo"
 
-// static xg_callback  wpt_s, wpt_lat, wpt_lon, wpt_e;
-// static xg_callback  wpt_addr /*, wpt_city, wpt_state, wpt_zip, wpt_country*/;
-#if 0
-static xg_tag_mapping gl_map[] = {
-  { wpt_s,     cb_start, "/ResultSet/Result" },
-  { wpt_lat,   cb_cdata, "/ResultSet/Result/Latitude" },
-  { wpt_lon,   cb_cdata, "/ResultSet/Result/Longitude" },
-  { wpt_addr,  cb_cdata, "/ResultSet/Result/Address" },
-  { wpt_addr,  cb_cdata, "/ResultSet/Result/City" },
-  { wpt_addr,  cb_cdata, "/ResultSet/Result/State" },
-  { wpt_addr,  cb_cdata, "/ResultSet/Result/Zip" },
-  { wpt_addr,  cb_cdata, "/ResultSet/Result/Country" },
-  { wpt_e,     cb_end,   "/ResultSet/Result" },
-  { nullptr,   (xg_cb_type)0,         nullptr}
-};
-#endif
-
 void
 YahooFormat::rd_init(const QString& fname)
 {
-abort();
-//  xml_init(fname, gl_map, nullptr);
+  xml_init(fname, build_xg_tag_map(this, gl_map), nullptr, nullptr, nullptr, true);
 }
 
 void
@@ -97,20 +80,3 @@ YahooFormat::wpt_addr(xg_string args, const QXmlStreamAttributes*)
   }
   wpt_tmp->notes += args;
 }
-#if 0
-ff_vecs_t yahoo_vecs = {
-  ff_type_file,
-  { ff_cap_read, ff_cap_none, ff_cap_none },
-  yahoo_rd_init,
-  nullptr,
-  yahoo_rd_deinit,
-  nullptr,
-  yahoo_read,
-  nullptr,
-  nullptr,
-  &yahoo_args,
-  CET_CHARSET_ASCII, 0 /* CET-REVIEW */
-  , NULL_POS_OPS,
-  nullptr
-};
-#endif
diff --git a/yahoo.h b/yahoo.h
index 1dc7fead0e3e6928470de0d4e3c0e4b0c805307a..16d846afb46d3bb330d002026d15ffedf80ea05a 100644 (file)
--- a/yahoo.h
+++ b/yahoo.h
 #ifndef YAHOO_H_INCLUDED_
 #define YAHOO_H_INCLUDED_
 
+#include <QtCore/QList>                 // for QList
 #include <QtCore/QString>               // for QString
-#include <QtCore/QStringList>           // for QStringList
-#include <QtCore/QXmlStreamAttributes>
+#include <QtCore/QVector>               // for QVector
+#include <QtCore/QXmlStreamAttributes>  // for QXmlStreamAttributes
 
 #include "defs.h"
 #include "format.h"                     // for Format
-#include "xmlgeneric.h"                     // for Format
+#include "xmlgeneric.h"                 // for xg_tag_map_entry, cb_cdata, XgFunctor, cb_end, cb_start
 
 class YahooFormat : public Format
 {
@@ -42,7 +43,8 @@ public:
     return ff_type_file;
   }
 
-  QVector<ff_cap> get_cap() const override {
+  QVector<ff_cap> get_cap() const override
+  {
     return {
       ff_cap_read,  // waypoints
       ff_cap_none,  // tracks
@@ -50,11 +52,13 @@ public:
     };
   }
 
-  QString get_encode() const override {
+  QString get_encode() const override
+  {
     return CET_CHARSET_ASCII;
   }
 
-  int get_fixed_encode() const override {
+  int get_fixed_encode() const override
+  {
     return 0;
   }
 
@@ -63,24 +67,34 @@ public:
   void rd_deinit() override;
 
 private:
-    Waypoint* wpt_tmp;
-    char* as;
-
-    QVector<arglist_t> yahoo_args = {
-      {
-        "addrsep", &as,
-        "String to separate concatenated address fields (default=\", \")",
-        ", ", ARGTYPE_STRING, ARG_NOMINMAX, nullptr
-      },
-    };
-    void wpt_s(const QString &, const QXmlStreamAttributes *);
-
- void wpt_e(const QString &, const QXmlStreamAttributes *);
- void wpt_lat(const QString &, const QXmlStreamAttributes *);
- void wpt_lon(const QString &, const QXmlStreamAttributes *);
- void wpt_addr(const QString &, const QXmlStreamAttributes *);
-// xg_tag_mapping gl_map[];
-
+  Waypoint* wpt_tmp;
+  char* as;
+
+  QVector<arglist_t> yahoo_args = {
+    {
+      "addrsep", &as,
+      "String to separate concatenated address fields (default=\", \")",
+      ", ", ARGTYPE_STRING, ARG_NOMINMAX, nullptr
+    },
+  };
+
+  void wpt_s(const QString&, const QXmlStreamAttributes*);
+  void wpt_e(const QString&, const QXmlStreamAttributes*);
+  void wpt_lat(const QString&, const QXmlStreamAttributes*);
+  void wpt_lon(const QString&, const QXmlStreamAttributes*);
+  void wpt_addr(const QString&, const QXmlStreamAttributes*);
+
+  QList<xg_functor_map_entry<YahooFormat>> gl_map = {
+    {&YahooFormat::wpt_s, cb_start, "/ResultSet/Result"},
+    {&YahooFormat::wpt_lat, cb_cdata, "/ResultSet/Result/Latitude"},
+    {&YahooFormat::wpt_lon, cb_cdata, "/ResultSet/Result/Longitude"},
+    {&YahooFormat::wpt_addr, cb_cdata, "/ResultSet/Result/Address"},
+    {&YahooFormat::wpt_addr, cb_cdata, "/ResultSet/Result/City"},
+    {&YahooFormat::wpt_addr, cb_cdata, "/ResultSet/Result/State"},
+    {&YahooFormat::wpt_addr, cb_cdata, "/ResultSet/Result/Zip"},
+    {&YahooFormat::wpt_addr, cb_cdata, "/ResultSet/Result/Country"},
+    {&YahooFormat::wpt_e, cb_end, "/ResultSet/Result"}
+  };
 };
 
 #endif //  YAHOO_H_INCLUDED_